import java.awt.image.DataBuffer;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBFragmentShader;
import org.lwjgl.opengl.ARBShaderObjects;
import org.lwjgl.opengl.ARBVertexShader;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;

/**
* The vertex and fragment shaders are setup when the box object is
* constructed. They are applied to the GL state prior to the box
* being drawn, and released from that state after drawing.
* @author Stephen Jones
*/
public class shader {
    /*
    * if the shaders are setup ok we can use shaders, otherwise we just
    * use default settings
    */
	private boolean enable = false;
    /*
    * program shader, to which is attached a vertex and fragment shaders.
    * They are set to 0 as a check because GL will assign unique int
    * values to each
    */
    private int shader=0;
    private int vertShader=0;
    private int fragShader=0;

	public shader() {
		init();
	}
	private void init(){
		enable=true;
        /*
        * create the shader program. If OK, create vertex
        * and fragment shaders
        */
        shader=ARBShaderObjects.glCreateProgramObjectARB();
        if(shader!=0){
            vertShader=createVertShader("/shaders/screen.vert");
            fragShader=createFragShader("/shaders/screen.frag");
        }else{
        	enable=false;
        }
        /*
        * if the vertex and fragment shaders setup successfully,
        * attach them to the shader program, link the shader program
        */
        if(vertShader !=0 && fragShader !=0){
            ARBShaderObjects.glAttachObjectARB(shader, vertShader);
            ARBShaderObjects.glAttachObjectARB(shader, fragShader);

            ARBShaderObjects.glLinkProgramARB(shader);
            if (ARBShaderObjects.glGetObjectParameteriARB(shader, ARBShaderObjects.GL_OBJECT_LINK_STATUS_ARB) == GL11.GL_FALSE) {
                printLogInfo(shader);
                enable=false;
            }
            ARBShaderObjects.glValidateProgramARB(shader);
            if (ARBShaderObjects.glGetObjectParameteriARB(shader, ARBShaderObjects.GL_OBJECT_VALIDATE_STATUS_ARB) == GL11.GL_FALSE) {
                printLogInfo(shader);
                enable=false;
            }
            // set the uniform to a default value
            GL20.glUniform1f(GL20.glGetUniformLocation(shader, "offset"), 0.0f);
            // set some static uniforms for the shaders
            int loopDurationUnf = GL20.glGetUniformLocation(shader, "loopDuration");
            int fragLoopDurUnf = GL20.glGetUniformLocation(shader, "fragLoopDuration");
            
            GL20.glUniform1f(loopDurationUnf, 5.0f);
            GL20.glUniform1f(fragLoopDurUnf, 10.0f);
            System.out.println("Shaders Compiled");
        }else{
        	enable=false;
        	System.out.println("Shaders NOT Compiled");
        }
        
	}
    private int createVertShader(String filename){
        //vertShader will be non zero if successfully created
        vertShader=ARBShaderObjects.glCreateShaderObjectARB(ARBVertexShader.GL_VERTEX_SHADER_ARB);
        //if created, convert the vertex shader code to a String
        if(vertShader==0){return 0;}
        String vertexCode="";
        String line;
        try{
        	InputStream is = getClass().getResourceAsStream(filename);
        	InputStreamReader isr = new InputStreamReader(is);
            BufferedReader reader=new BufferedReader(isr);
            while((line=reader.readLine())!=null){
                vertexCode+=line + "\n";
            }
        }catch(Exception e){
        	e.printStackTrace();
            System.out.println("Fail reading vertex shading code");
            return 0;
        }
        /*
        * associate the vertex code String with the created vertex shader
        * and compile
        */
        ARBShaderObjects.glShaderSourceARB(vertShader, vertexCode);
        ARBShaderObjects.glCompileShaderARB(vertShader);
        //if there was a problem compiling, reset vertShader to zero
        if (ARBShaderObjects.glGetObjectParameteriARB(vertShader, ARBShaderObjects.GL_OBJECT_COMPILE_STATUS_ARB) == GL11.GL_FALSE) {
        	System.out.println("Problem in Vertex shader");
        	printLogInfo(vertShader);
            vertShader=0;
        }
        //if zero we won't be using the shader
        return vertShader;
    }
    private int createFragShader(String filename){
        fragShader=ARBShaderObjects.glCreateShaderObjectARB(ARBFragmentShader.GL_FRAGMENT_SHADER_ARB);
        if(fragShader==0){return 0;}
            String fragCode="";
            String line;
        try{
        	InputStream is = getClass().getResourceAsStream(filename);
        	InputStreamReader isr = new InputStreamReader(is);
            BufferedReader reader=new BufferedReader(isr);
            while((line=reader.readLine())!=null){
                fragCode+=line + "\n";
            }
        }catch(Exception e){
            System.out.println("Fail reading fragment shading code");
            return 0;
        }
        ARBShaderObjects.glShaderSourceARB(fragShader, fragCode);
        ARBShaderObjects.glCompileShaderARB(fragShader);
        if (ARBShaderObjects.glGetObjectParameteriARB(fragShader, ARBShaderObjects.GL_OBJECT_COMPILE_STATUS_ARB) == GL11.GL_FALSE) {
        	System.out.println("Problem in Fragment shader");
            printLogInfo(fragShader);
            fragShader=0;
        }
        return fragShader;
    }
    
    private static boolean printLogInfo(int obj){
        IntBuffer iVal = BufferUtils.createIntBuffer(1);
        ARBShaderObjects.glGetObjectParameterARB(obj,ARBShaderObjects.GL_OBJECT_INFO_LOG_LENGTH_ARB, iVal);

        int length = iVal.get();
        if (length > 1) {
            // We have some info we need to output.
            ByteBuffer infoLog = BufferUtils.createByteBuffer(length);
            iVal.flip();
            ARBShaderObjects.glGetInfoLogARB(obj, iVal, infoLog);
            byte[] infoBytes = new byte[length];
            infoLog.get(infoBytes);
            String out = new String(infoBytes);
            System.out.println("Info log:\n"+out);
        }
        else return true;
        return false;
    }
	
    public void setUniform(String name, float value){
    	GL20.glUniform1f(GL20.glGetUniformLocation(shader, name), value);
    }
    public void setUniformMatrix(String name, float[] theMatrix){
    	ByteBuffer buf1 = ByteBuffer.allocateDirect(theMatrix.length* 4);
    	buf1.order(ByteOrder.nativeOrder());
    	FloatBuffer buffer = buf1.asFloatBuffer();
    	buffer.put(theMatrix,0,theMatrix.length);
    	buffer.position(0);
    	//buf1.rewind();
    	GL20.glUniformMatrix4(GL20.glGetUniformLocation(shader, name), false, buffer);
    }
    public int TheShader(){
    	return shader;
    }
	public boolean enabled() {
		return enable;
	}
}
